#ifndef THUMBNAILPAINTER_H
#define THUMBNAILPAINTER_H

#include <QObject>
#include <QPainter>
#include <QDebug>
#include <complex>
#include <algorithm>

class AbstractThumbnailPainter : public QObject
{
    Q_OBJECT
public:
    AbstractThumbnailPainter(const QSize& size, QObject *parent = nullptr) { Q_UNUSED(size) Q_UNUSED(parent)}

    virtual QPixmap pixmap() const = 0;

    void setPlotColor(const QColor& clr) { m_plotColor = clr; }
    QColor plotColor() const { return m_plotColor; }

public slots:
    virtual void start() = 0;

signals:
    void finished();

protected:
    QColor m_plotColor = Qt::black;
};

template <typename T>
class ThumbnailPainterTempl : public AbstractThumbnailPainter
{
public:
    ThumbnailPainterTempl(const QVector<T>& data, const QSize& size, QObject* parent = nullptr) :
                        AbstractThumbnailPainter(size, parent), m_data(data), m_size(size) {}

    QPixmap pixmap() const { return m_pixmap; }

protected:
    QVector<T> m_data;
    QSize m_size;
    QPixmap m_pixmap;
};

template <typename T>
class ThumbnailPainter : public ThumbnailPainterTempl<T>
{
public:
    ThumbnailPainter(const QVector<T>& data, const QSize& size, QObject* parent = nullptr) :
                    ThumbnailPainterTempl(data, size, parent) {}

    void start()
    {
        QPixmap pixmap(m_size.width(), m_size.height());
        pixmap.fill(Qt::transparent);

        // for performance consideractions, should move this part to thread
        // colData is obsolete, so it crashes
        if (m_data.size() != 0) {
            QPainter painter(&pixmap);
            if (!painter.isActive()) {
                m_pixmap = pixmap;
                emit finished();
                return;
            }

            typedef QVector<T>::const_iterator ConstIter;
            std::pair<ConstIter, ConstIter> res = std::minmax_element(m_data.begin(), m_data.end());
            T min = *res.first;
            T max = *res.second;

            // draw curve
            painter.setPen(QPen(m_plotColor, 1));

            QList<QPointF> pots;

            qreal range = max - min;
            int extra = 10;

            if (range != 0) {
                int contentHeight = pixmap.height() - 2 * extra;
                int contentWidth = pixmap.width() - 2 * extra;
                qreal incre = (qreal) contentWidth / m_data.size();
                qreal startX = extra;

                for (int index = 0; index < m_data.size()- 1; ++index) {
                    qreal value = m_data.at(index);
                    qreal dY1 = (qreal)contentHeight / range * (value - min);
                    qreal y1 = contentHeight - dY1 + extra;
                    qreal nextVal = m_data.at(index+1);
                    qreal dY2 = (qreal)contentHeight / range * (nextVal - min);
                    qreal y2 = contentHeight - dY2 + extra;
                    QPointF dataPot1(startX, y1);
                    QPointF dataPot2(startX + incre, y2);
                    startX += incre;
                    pots.push_back(dataPot1);
                    pots.push_back(dataPot2);
                }

                painter.drawLines(pots.toVector());
            } else {
                // if range is zero, it means all the data are the same, so just draw a horizontal line
                QPointF leftPot(5, m_size.height() / 2);
                QPointF rightPot(m_size.width() - 5, m_size.height() / 2);
                painter.drawLine(leftPot, rightPot);
            }

            painter.end();
        }

        m_pixmap = pixmap;
        emit finished();
    }
};

template <typename T>
static bool drawPoints(const QVector<std::complex<T> >& data, QPixmap& pixmap, const QColor& clr)
{
    if (data.isEmpty())
        return false;

    QPainter painter(&pixmap);
    if (!painter.isActive())
        return false;

    painter.setPen(QPen(clr, 1));

    int planeWidth = qMin(pixmap.width(), pixmap.height());
    int halfPlaneWidth = planeWidth / 2;

    auto realLess = [](const std::complex<T>& a, const std::complex<T>& b) { return a.real() < b.real(); };
    auto imagLess = [](const std::complex<T>& a, const std::complex<T>& b) { return a.imag() < b.imag(); };

    typedef QVector<std::complex<T> >::const_iterator ConstIter;
    std::pair<ConstIter, ConstIter> realRes = std::minmax_element(data.begin(), data.end(), realLess);
    std::pair<ConstIter, ConstIter> imagRes = std::minmax_element(data.begin(), data.end(), imagLess);
    T minReal = (*realRes.first).real();
    T maxReal = (*realRes.second).real();
    T minImag = (*imagRes.first).imag();
    T maxImag = (*imagRes.second).imag();
    T maxAbsReal = qMax(qAbs(minReal), qAbs(maxReal));
    T maxAbsImag = qMax(qAbs(minImag), qAbs(maxImag));
    T maxValue = qMax(maxAbsReal, maxAbsImag);
    qreal pixelPerUnit = (qreal)halfPlaneWidth / maxValue;

    QPoint origin = pixmap.rect().center();

    // draw vertical & horizontal lines
    painter.drawLine(QPointF(origin.x() - halfPlaneWidth, origin.y()), QPointF(origin.x() + halfPlaneWidth, origin.y()));
    painter.drawLine(QPointF(origin.x(), origin.y() - halfPlaneWidth), QPointF(origin.x(), origin.y() + halfPlaneWidth));

    // draw points
    for (int i = 0; i < data.size(); ++i) {
        const std::complex<T>& val = data.at(i);

        QPointF pot(origin.x() + val.real() * pixelPerUnit, origin.y() + val.imag() * pixelPerUnit);
        painter.drawEllipse(pot, 1, 1);
    }
    painter.end();
    return true;
}

template <typename T>
class ThumbnailPainter<std::complex<T> > : public ThumbnailPainterTempl<std::complex<T> >
{
public:
    ThumbnailPainter(const QVector<std::complex<T>>& data, const QSize& size, QObject* parent = nullptr) :
                                ThumbnailPainterTempl(data, size, parent) {}

public slots:
    void start()
    {
        // TODO: plot float complex graph
        QPixmap pixmap(m_size.width(), m_size.height());
        pixmap.fill(Qt::transparent);
        drawPoints(m_data, pixmap, m_plotColor);
        m_pixmap = pixmap;
        emit finished();
    }
};

#endif // THUMBNAILPAINTER_H
